package modelo;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.thoughtworks.xstream.XStream;



public class DataBaseManager {
	
	public Object persisteEntidad(Object obj){
		//check if escuela.xml existe : no
		//check si escuela existe : no		
		Object resultado = obj; // por si en el futuro necesito devolver el objecto con un ID autocreado x ej.
	    String plural = this.getStringValue("getPlural", obj);
	    String singular = this.getStringValue("getSingular", obj);
	    String keyfield = this.getStringValue("getKeyField", obj);
	    String xmlpath = this.getStringValue("getPlural", obj)+".xml";
	    
		boolean exists = (new File(xmlpath)).exists();
		if (exists) {	
		 
			Document doc = this.creaDocument(xmlpath);
		    
		    NodeList nodes = this.buscarElement(doc, obj);			  	
		    	    	
	    	if(nodes.getLength() == 0){
	    		if(keyfield.equals("id")){
	    			int id = this.getMaxId(doc, obj);	    			
	    			Object[] input = {new Integer(id +1)};
	    			this.setGenerico("setId", obj, input);
	    		}
    			doc = this.agregaObjADoc(doc, obj);  		
    			this.escribirXML(doc);
    			
		    }else{
		    	System.out.println("Ya existe un/a "+singular+" con "+keyfield+": "+this.getStringValue(this.getMethod(keyfield), obj));
		    	resultado = null;
		    }		    
		} else {		
			// root elements
			Document doc = this.creaDocument("");
			Element rootElement = doc.createElement(plural);
			doc.appendChild(rootElement);
			if(keyfield.equals("id")){
				Object[] input = {new Integer(1)};
				this.setGenerico("setId", obj, input);
			}
			doc = this.agregaObjADoc(doc, obj);
			
			this.escribirXML(doc);		    
		} 
		
		return resultado;
		
	}
	
	public Object actualizarEntidad(Object obj) {
		String xmlpath = this.getStringValue("getPlural", obj)+".xml";
		String singular = this.getStringValue("getSingular", obj);
	    String keyfield = this.getStringValue("getKeyField", obj);
		Document doc = this.creaDocument(xmlpath);
		NodeList nodes = this.buscarElement(doc, obj);
		Object resultado = null;
		if(nodes.getLength() == 0){
			System.out.println("No existe un/a "+singular+" con "+keyfield+": "+this.getStringValue(this.getMethod(keyfield), obj));
		}else{
			Element elem = (Element)nodes.item(0);
			elem.getParentNode().removeChild(elem);
			
			doc = this.agregaObjADoc(doc, obj);
			
			this.escribirXML(doc);
			resultado = obj;
		}		
		return resultado;
	}
	
	public Object eliminarEntidad(Object obj){
		String xmlpath = this.getStringValue("getPlural", obj)+".xml";
		String singular = this.getStringValue("getSingular", obj);
	    String keyfield = this.getStringValue("getKeyField", obj);
		Document doc = this.creaDocument(xmlpath);		
		NodeList nodes = this.buscarElement(doc, obj);
		Object resultado = false;
		if(nodes.getLength() == 0){
			System.out.println("No existe un/a "+singular+" con "+keyfield+": "+this.getStringValue(this.getMethod(keyfield), obj));
			
		}else{
			Element elem = (Element)nodes.item(0);			
			elem.getParentNode().removeChild(elem);
			this.escribirXML(doc);
			resultado = true;			
		}
		return resultado;
				
	}
	
	// get Entidad por clave
	public Object getEntidad(Object obj){				
		Object result = null;
		String xmlpath = this.getStringValue("getPlural", obj)+".xml";
		String singular = this.getStringValue("getSingular", obj);		
	    String keyfield = this.getStringValue("getKeyField", obj);
		Document doc = this.creaDocument(xmlpath);
		NodeList nodes = this.buscarElement(doc, obj);
		
		if(nodes.getLength() == 0){
			System.out.println("No existe un/a "+singular+" con "+keyfield+": "+this.getStringValue(this.getMethod(keyfield), obj));
		}else{			
			result = this.XMLtoObject(obj, nodes.item(0));
		}		
			
		return result;
	}
	
	public List<Object> listarEntidades(Object obj){
		String xmlpath = this.getStringValue("getPlural", obj)+".xml";		
	    ArrayList<Object> resultado = new ArrayList<Object>();
	    boolean exists = (new File(xmlpath)).exists();
		if (exists) {
			Document doc = this.creaDocument(xmlpath);
			Node root = doc.getFirstChild();
			NodeList nodes = root.getChildNodes();
			for (int i = 0; i < nodes.getLength(); i++) {			
				Object objfromxml = this.XMLtoObject(obj, nodes.item(i));
				if(!(objfromxml == null))
					resultado.add(objfromxml);
			}			
		}		
		return resultado;
	}
	
	public List<Object> listarEntidadesXCampos(Object obj,Map campos){
		String xmlpath = this.getStringValue("getPlural", obj)+".xml";
	    ArrayList<Object> resultado = new ArrayList<Object>();
	    boolean exists = (new File(xmlpath)).exists();
		if (exists) {
			Document doc = this.creaDocument(xmlpath);
			
			NodeList nodes = this.buscarElementXCampos(doc, obj, campos);
			
			if(nodes.getLength() == 0){
				System.out.println("No se encontraron resultados");
				
			}else{
				for (int i = 0; i < nodes.getLength(); i++) {			
					Object objfromxml = this.XMLtoObject(obj, nodes.item(i));
					if(!(objfromxml == null))
						resultado.add(objfromxml);
				}			
			}					
		}		
		return resultado;
	}
	
	private String getMethod(String name){
		return  "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
	}
	
		
	private String getStringValue(String method_name,Object obj){
		Method method = null;
		Object value = null;
		try {
			method = obj.getClass().getMethod(method_name);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			value =method.invoke(obj);
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return (value == null)?"":value.toString();		
	}
	
	private boolean setGenerico(String method_name,Object obj, Object[] args){
		Method method = null;
		boolean value = false;
		//esto va a funcionar para metodos que reciben como parametros un int  
		Class[] par=new Class[1];
	    par[0]=Integer.TYPE;
	    
	    
		try {
			method = obj.getClass().getMethod(method_name,par);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			method.invoke(obj,args);
			value = true;
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return value;		
	}
	
	private void escribirXML(Document doc){
		String xmlname = doc.getFirstChild().getNodeName();
		TransformerFactory tranFactory = TransformerFactory.newInstance();
        Transformer aTransformer = null;
		try {
			aTransformer = tranFactory.newTransformer();
		} catch (TransformerConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        aTransformer.setOutputProperty("indent", "yes");

        Source src = new DOMSource(doc);
        Result dest = new StreamResult(xmlname+".xml");
        try {
			aTransformer.transform(src, dest);
		} catch (TransformerException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}   
	}
	
	private Document agregaObjADoc(Document doc,Object obj){
		
		//aca quizas se pueda usar xstream !!!
		String classname,value, name;		
		value = "";
		classname = obj.getClass().getName();		
		Element elem =  doc.createElement(classname.substring(classname.lastIndexOf(".")+1).toLowerCase());
		Node root = doc.getFirstChild();
		root.insertBefore(elem, root.getLastChild());
		Field[] fields = obj.getClass().getDeclaredFields();
		Element elementoHijo = null;
		
		for (Field field : fields) {
			name = field.getName();
			//type = field.getType().getName();			
			value = this.getStringValue(this.getMethod(name), obj);			
			elementoHijo =  doc.createElement(name);
			elementoHijo.appendChild(doc.createTextNode(value));
			elem.insertBefore(elementoHijo,elem.getLastChild());		
		}
		
		return doc;
		
	}
	
	private NodeList buscarElement(Document doc, Object obj){		
	    
	    String singular = this.getStringValue("getSingular", obj);
	    String keyfield = this.getStringValue("getKeyField", obj); 		   

		
	    XPathFactory factory = XPathFactory.newInstance();
	    XPath xpath = factory.newXPath();
	    XPathExpression expr = null;
		try {
			expr = xpath.compile("//"+singular+"["+keyfield+"='"+this.getStringValue(this.getMethod(keyfield), obj)+"']");
			//System.out.println("//"+singular+"["+keyfield+"='"+this.getStringValue(this.getMethod(keyfield), obj)+"']");
		} catch (XPathExpressionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	    Object result = null;
		try {
			result = expr.evaluate(doc, XPathConstants.NODESET);
		} catch (XPathExpressionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	    NodeList nodes = (NodeList) result;
	    return nodes;    	
	}
	
	private NodeList buscarElementXCampos(Document doc, Object obj, Map campos){		
	    
	    String singular = this.getStringValue("getSingular", obj);
		String from = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
		String to = "abcdefghijklmnopqrstuvwxyz";
	    String query = "";		
	    XPathFactory factory = XPathFactory.newInstance();
	    XPath xpath = factory.newXPath();
	    XPathExpression expr = null;
	    
	    Iterator it = campos.entrySet().iterator();
	    while (it.hasNext()) {
	        Map.Entry pairs = (Map.Entry)it.next();
	        
	        query+=query.length()==0?"":" and ";
	        query+="starts-with(translate("+pairs.getKey() +",'"+from+"','"+to+"'),'" + pairs.getValue().toString().toLowerCase()+"')";
	     }    
	    
		try {
			expr = xpath.compile("//"+singular+"["+query+"]");
			System.out.println("//"+singular+"["+query+"]");
		} catch (XPathExpressionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	    Object result = null;
		try {
			result = expr.evaluate(doc, XPathConstants.NODESET);
		} catch (XPathExpressionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	    NodeList nodes = (NodeList) result;
	    return nodes;    	
	}
	
	private int getMaxId(Document doc, Object obj){		
	    
	    String singular = this.getStringValue("getSingular", obj);		
	    XPathFactory factory = XPathFactory.newInstance();
	    XPath xpath = factory.newXPath();
	    XPathExpression expr = null;
	    	    
		try {
			expr = xpath.compile("//"+singular+"/id[not(. < ../../"+singular+"/id)][1]");
			System.out.println("//"+singular+"/id[not(. < ../../"+singular+"/id)][1]");
		} catch (XPathExpressionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	    Double result = new Double(0);
		try {
			result = (Double) expr.evaluate(doc, XPathConstants.NUMBER);			
		} catch (XPathExpressionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
			    
	    return result.intValue();    	
	}
	
	
	private Document creaDocument(String xmlpath){
		DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
	    domFactory.setNamespaceAware(true); // never forget this!
	    DocumentBuilder docBuilder = null;		    
	    try {
	    	docBuilder = domFactory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	    
	    Document doc = null;
	    if(xmlpath.equals("")){
	    	doc = docBuilder.newDocument();
	    }else{
	    				   
		    try {
				doc = docBuilder.parse(xmlpath);
			} catch (SAXException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}	    	
	    }
	    
		return doc;
	}
	
	private Object XMLtoObject(Object obj, Node elem){
		XStream xstream = new XStream();
		String singular = this.getStringValue("getSingular", obj);	    
	    Object resultado = null;

		if(elem.getNodeType() == Node.ELEMENT_NODE)	{				
			try {
				xstream.alias(singular, Class.forName("modelo."+singular.substring(0, 1).toUpperCase()+singular.substring(1)));
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			TransformerFactory transFactory = TransformerFactory.newInstance();
			Transformer transformer = null;
			try {
				transformer = transFactory.newTransformer();
			} catch (TransformerConfigurationException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			StringWriter buffer = new StringWriter();
			transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
			try {
				transformer.transform(new DOMSource(elem), new StreamResult(buffer));
			} catch (TransformerException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			String str = buffer.toString();
			resultado = xstream.fromXML(str);
		}		
		
		return resultado;
	}
	

}
